package net.cyue.nest.nestjava.platform.tomcat;


import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import net.cyue.nest.nestjava.common.data.NestApplicationOption;
import net.cyue.nest.nestjava.common.data.http.CORSOption;
import net.cyue.nest.nestjava.common.enums.HTTPStatus;
import net.cyue.nest.nestjava.common.enums.RequestMethod;
import net.cyue.nest.nestjava.common.interfaces.http.IRequestHandler;
import net.cyue.nest.nestjava.core.adapter.AbstractHTTPAdapter;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

@MultipartConfig
class DefaultServlet extends HttpServlet {
  private IRequestHandler handler;
  private RequestMethod[] allowMethods;

  public DefaultServlet(IRequestHandler handler) {
    this(handler, new RequestMethod[]{ RequestMethod.ALL });
  }
  public DefaultServlet(IRequestHandler handler, RequestMethod[] allowMethods) {
    this.handler = handler;
    this.allowMethods = allowMethods;
  }

  @Override
  public void service(
    ServletRequest servletRequest,
    ServletResponse servletResponse
  ) throws IOException {
    servletRequest.setCharacterEncoding("UTF-8");
    servletResponse.setCharacterEncoding("UTF-8");

    if (
      this.allowMethods == null ||
      this.allowMethods[0].equals(RequestMethod.ALL)
    ) {
      this.handler.handle(
        new TomcatHTTPRequest(servletRequest),
        new TomcatHTTPResponse(servletResponse)
      );
      return;
    }

    final var request = (HttpServletRequest) servletRequest;
    final var method = RequestMethod.valueOf(request.getMethod().toUpperCase());
    for (RequestMethod m : this.allowMethods) {
      if (m.equals(method)) {
        this.handler.handle(
          new TomcatHTTPRequest(servletRequest),
          new TomcatHTTPResponse(servletResponse)
        );
        break;
      }
    }
  }

  @Override
  public void destroy() {
    this.handler = null;
    this.allowMethods = null;
  }
}


public class TomcatAdapter
  extends AbstractHTTPAdapter<Tomcat>
{
  // private static int SERVLET_COUNT = 0;
  private static String getNewServletName(String path) {
    // return "Servlet" + (++TomcatAdapter.SERVLET_COUNT);
    String[] parts = path.split("/");
    return "servlet" + String.join("-", parts);
  }

  private final Context context;

  public TomcatAdapter()
  {
    this.httpServer = new Tomcat();
    this.httpServer.enableNaming();

    final var webappPath = new File(".").getAbsolutePath();
    this.context = this.httpServer.addWebapp(
      "",
      webappPath
    );
    this.context.setRequestCharacterEncoding("UTF-8");
    this.context.setResponseCharacterEncoding("UTF-8");
  }

  private void addRoute(String path, IRequestHandler handler, RequestMethod[] methods) {
    final var servletName = TomcatAdapter.getNewServletName(path);
    final var servlet = new DefaultServlet(handler, methods);

    // this.context.addServletMappingDecoded(path, servletName);
    final var wrapper = this.context.createWrapper();
    wrapper.setName(servletName);
    wrapper.setServlet(servlet);
    this.context.addChild(wrapper);
    wrapper.addMapping(path);

    System.out.println();
    System.out.println("servlet: " + servletName);
    System.out.println("route: " + path);
    System.out.println("methods: " + Arrays.toString(methods));
  }

  @Override
  public void use(IRequestHandler handler) {
    this.addRoute("/", handler, null);
  }

  @Override
  public void use(String path, IRequestHandler handler) {
    this.addRoute(path, handler, null);
  }

  @Override
  public void get(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.GET});
  }

  @Override
  public void get(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.GET});
  }

  @Override
  public void post(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.POST});
  }

  @Override
  public void post(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.POST});
  }

  @Override
  public void head(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.HEAD});
  }

  @Override
  public void head(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.HEAD});
  }

  @Override
  public void delete(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.DELETE});
  }

  @Override
  public void delete(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.DELETE});
  }

  @Override
  public void put(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.PUT});
  }

  @Override
  public void put(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.PUT});
  }

  @Override
  public void patch(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.PATCH});
  }

  @Override
  public void patch(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.PATCH});
  }

  @Override
  public void all(IRequestHandler handler) {
    this.addRoute("/", handler, null);
  }

  @Override
  public void all(String path, IRequestHandler handler) {
    this.addRoute(path, handler, null);
  }

  @Override
  public void options(IRequestHandler handler) {
    this.addRoute("/", handler, new RequestMethod[]{RequestMethod.OPTIONS});
  }

  @Override
  public void options(String path, IRequestHandler handler) {
    this.addRoute(path, handler, new RequestMethod[]{RequestMethod.OPTIONS});
  }

  @Override
  public void listen(int port) {
    try {
      final var connector = new Connector();
      connector.setPort(port);
      connector.setMaxPostSize(0);
      this.httpServer.setConnector(connector);

      this.httpServer.start();
      this.httpServer.getServer().await();
    } catch (LifecycleException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public void listen(String port) {
    this.listen(Integer.parseInt(port));
  }

  @Override
  public void setErrorHandler(HTTPStatus status, IRequestHandler handler) {

  }

  @Override
  public void setNotFoundHandler(IRequestHandler handler) {

  }

  @Override
  public void initHTTPServer(NestApplicationOption option) {

  }

  @Override
  public void close() {
    try {
      this.httpServer.stop();
    } catch (LifecycleException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public void enableCORS(CORSOption option) {
  }
}
